#include <iostream>
#include <cxxabi.h>

#include <plc2llvm/Visitor/Visitor.h>
#include <plc2llvm/utils/Log.h>
#include <plc2llvm/Visitor/strategy/pou_decl/pou_decl.h>
#include <plc2llvm/Visitor/strategy/statement/stmt.h>
#include <plc2llvm/Visitor/strategy/var_decls/var_decl.h>
#include "plc2llvm/Visitor/strategy/expression/expression.h"
#include <plc2llvm/TypeSystem/Basic/BasicInclude.h>
#include "llvm/IR/Type.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/Constant.h"
#include "llvm/Support/raw_ostream.h"


Visitor::Visitor(plcst::PLCSTParser *parser, llvm::StringRef moduleName, std::string outputPath) 
: parser(parser), tokens(parser->getTokenStream()), outputPath(std::move(outputPath))
{
    this->module = new llvm::Module(moduleName, this->context);
    this->builder = new llvm::IRBuilder<>(this->context);
    this->creatAllBasicType();
}

void Visitor::creatAllBasicType() {
    auto globScope = plcst::ScopeManager::getScopeManager().getGlobalScope();

    #define makeBasicType(tk) new plcst::tk##Type(#tk)

    #define CREATE_INTEGER_AND_INSERT(x) \
        auto x##ty = makeBasicType(x); \
        x##ty->llvmty = llvm::IntegerType::get(this->context, x##ty->getNBits()); \
        x##ty->initllvmValue = llvm::ConstantInt::get(x##ty->llvmty, 0); \
        std::shared_ptr<plcst::Type> shared_##x##ty(x##ty); \
        globScope->insert(shared_##x##ty); 

    // integer
    CREATE_INTEGER_AND_INSERT(SINT);
    CREATE_INTEGER_AND_INSERT(INT);
    CREATE_INTEGER_AND_INSERT(DINT);
    CREATE_INTEGER_AND_INSERT(LINT);
    CREATE_INTEGER_AND_INSERT(USINT);
    CREATE_INTEGER_AND_INSERT(UINT);
    CREATE_INTEGER_AND_INSERT(UDINT);
    CREATE_INTEGER_AND_INSERT(ULINT);

    // TODO: find llvm::FPtype
    CREATE_INTEGER_AND_INSERT(REAL);
    CREATE_INTEGER_AND_INSERT(LREAL);


    CREATE_INTEGER_AND_INSERT(TIME);
    CREATE_INTEGER_AND_INSERT(LTIME);
    CREATE_INTEGER_AND_INSERT(DATE);
    CREATE_INTEGER_AND_INSERT(LDATE);
    CREATE_INTEGER_AND_INSERT(TOD);
    CREATE_INTEGER_AND_INSERT(LTOD);
    CREATE_INTEGER_AND_INSERT(DT);
    CREATE_INTEGER_AND_INSERT(LDT);

    // TODO: string type
    CREATE_INTEGER_AND_INSERT(STRING);
    CREATE_INTEGER_AND_INSERT(WSTRING);
    CREATE_INTEGER_AND_INSERT(CHAR);
    CREATE_INTEGER_AND_INSERT(WCHAR);

    CREATE_INTEGER_AND_INSERT(BOOL);
    CREATE_INTEGER_AND_INSERT(BYTE);
    CREATE_INTEGER_AND_INSERT(WORD);
    CREATE_INTEGER_AND_INSERT(DWORD);
    CREATE_INTEGER_AND_INSERT(LWORD);

    #undef CREATE_INTEGER_AND_INSERT
    #undef makeBasicType
}

const antlr4::TokenStream *Visitor::getTokenStream()
{
    return this->tokens;
}

// 释放单例类：策略类、作用域栈
Visitor::~Visitor()
{
    std::error_code ec;
    llvm::raw_fd_ostream out(outputPath, ec, llvm::sys::fs::OpenFlags(1));
    if (ec) {
        llvm::errs() << "Could not open file: " << ec.message();
    }
    module->print(out, nullptr);
}

std::any Visitor::visitStartpoint(plcst::PLCSTParser::StartpointContext* ctx) {
    return pou_decl::visitStartpoint(ctx, this);
}

// expression::
std::any Visitor::visitConstant_Expr(plcst::PLCSTParser::Constant_ExprContext *ctx)
{
    return expression::visitConstant_Expr(ctx, this);
}

std::any Visitor::visitExpression(plcst::PLCSTParser::ExpressionContext *ctx)
{
    return expression::visitExpression(ctx, this);
}

std::any Visitor::visitXor_Expr(plcst::PLCSTParser::Xor_ExprContext *ctx)
{
    return expression::visitXor_Expr(ctx, this);
}

std::any Visitor::visitAnd_Expr(plcst::PLCSTParser::And_ExprContext *ctx)
{
    return expression::visitAnd_Expr(ctx, this);
}

std::any Visitor::visitCompare_Expr(plcst::PLCSTParser::Compare_ExprContext *ctx)
{
    return expression::visitCompare_Expr(ctx, this);
}

std::any Visitor::visitEqu_Expr(plcst::PLCSTParser::Equ_ExprContext *ctx)
{
    return expression::visitEqu_Expr(ctx, this);
}

std::any Visitor::visitAdd_Expr(plcst::PLCSTParser::Add_ExprContext *ctx)
{
    return expression::visitAdd_Expr(ctx, this);
}

std::any Visitor::visitTerm(plcst::PLCSTParser::TermContext *ctx)
{
    return expression::visitTerm(ctx, this);
}

std::any Visitor::visitPower_Expr(plcst::PLCSTParser::Power_ExprContext *ctx)
{
    return expression::visitPower_Expr(ctx, this);
}

std::any Visitor::visitUnary_Expr(plcst::PLCSTParser::Unary_ExprContext *ctx)
{
    return expression::visitUnary_Expr(ctx, this);
}

std::any Visitor::visitPrimary_Expr(plcst::PLCSTParser::Primary_ExprContext *ctx)
{
    return expression::visitPrimary_Expr(ctx, this);
}

std::any Visitor::visitBit_Str_Literal(plcst::PLCSTParser::Bit_Str_LiteralContext *ctx)
{
    return this->visitChildren(ctx); // TODO
}

std::any Visitor::visitIdentifier(plcst::PLCSTParser::IdentifierContext* ctx) {
    return expression::visitIdentifier(ctx, this);
}

std::any Visitor::visitParen_Surrounded_Expr(plcst::PLCSTParser::Paren_Surrounded_ExprContext *ctx) {
	return expression::visitParen_Surrounded_Expr(ctx, this);
}

std::any Visitor::visitInt_Literal(plcst::PLCSTParser::Int_LiteralContext *ctx) {
	return expression::visitInt_Literal(ctx, this);
}

std::any Visitor::visitReal_Literal(plcst::PLCSTParser::Real_LiteralContext *ctx) {
	return expression::visitReal_Literal(ctx, this);
}

std::any Visitor::visitEnum_Value(plcst::PLCSTParser::Enum_ValueContext *ctx) {
	return expression::visitEnum_Value(ctx, this);
}

std::any Visitor::visitVariable_Access(plcst::PLCSTParser::Variable_AccessContext *ctx) {
	return expression::visitVariable_Access(ctx, this);
}

std::any Visitor::visitFunc_Call(plcst::PLCSTParser::Func_CallContext *ctx) {
	return expression::visitFunc_Call(ctx, this);
}

std::any Visitor::visitRef_Value(plcst::PLCSTParser::Ref_ValueContext *ctx) {
	return expression::visitRef_Value(ctx, this);
}

std::any Visitor::visitConstant(plcst::PLCSTParser::ConstantContext *ctx) {
	return expression::visitConstant(ctx, this);
}
// end of expression

std::any Visitor::visitIteration_Stmt(plcst::PLCSTParser::Iteration_StmtContext *ctx)
{
    return stmt::visitIteration_Stmt(ctx, this);
}

std::any Visitor::visitFor_Stmt(plcst::PLCSTParser::For_StmtContext *ctx)
{
    return stmt::visitFor_Stmt(ctx, this);
}

std::any Visitor::visitAssign_Stmt(plcst::PLCSTParser::Assign_StmtContext *ctx)
{
    return stmt::visitAssign_Stmt(ctx, this);
}

std::any Visitor::visitVar_Assign(plcst::PLCSTParser::Var_AssignContext *ctx)
{
    return stmt::visitVar_Assign(ctx, this);
}

std::any Visitor::visitVariable(plcst::PLCSTParser::VariableContext *ctx)
{
    return stmt::visitVariable(ctx, this);
}

std::any Visitor::visitSymbolic_Variable(plcst::PLCSTParser::Symbolic_VariableContext *ctx)
{
    return stmt::visitSymbolic_Variable(ctx, this);
}

std::any Visitor::visitVar_Access(plcst::PLCSTParser::Var_AccessContext *ctx)
{
    return stmt::visitVar_Access(ctx, this);
}

std::any Visitor::visitVar_Decls(plcst::PLCSTParser::Var_DeclsContext *ctx)
{
    return var_decl::visitVar_Decls(ctx, this);
}

std::any Visitor::visitVar_Decl_Init(plcst::PLCSTParser::Var_Decl_InitContext *ctx)
{
    return var_decl::visitVar_Decl_Init(ctx, this);
}

std::any Visitor::visitVariable_List(plcst::PLCSTParser::Variable_ListContext *ctx)
{
    return var_decl::visitVariable_List(ctx, this);
}

std::any Visitor::visitVariable_Name(plcst::PLCSTParser::Variable_NameContext *ctx)
{
    return var_decl::visitVariable_Name(ctx, this);
}

std::any Visitor::visitSimple_Spec_Init(plcst::PLCSTParser::Simple_Spec_InitContext *ctx)
{
    return var_decl::visitSimple_Spec_Init(ctx, this);
}

std::any Visitor::visitStmt_List(plcst::PLCSTParser::Stmt_ListContext *ctx)
{
    return stmt::visitStmt_List(ctx, this);
}

std::any Visitor::visitStmt(plcst::PLCSTParser::StmtContext *ctx) 
{
    return stmt::visitStmt(ctx, this);
}

std::any Visitor::visitControl_Variable(plcst::PLCSTParser::Control_VariableContext *ctx)
{
    return this->visitChildren(ctx);
}

std::any Visitor::visitFor_List(plcst::PLCSTParser::For_ListContext *ctx)
{
    return stmt::visitFor_List(ctx, this);
}

std::any Visitor::visitRef_Deref(plcst::PLCSTParser::Ref_DerefContext *ctx)
{
    return this->visitChildren(ctx);
}

std::any Visitor::visitMulti_Elem_Var(plcst::PLCSTParser::Multi_Elem_VarContext *ctx)
{
    return this->visitChildren(ctx);
}

std::any Visitor::visitProg_Decl(plcst::PLCSTParser::Prog_DeclContext *ctx)
{
    return pou_decl::visitProg_Decl(ctx, this);
}

std::any Visitor::visitFunc_Decl(plcst::PLCSTParser::Func_DeclContext *ctx)
{
    return pou_decl::visitFunc_Decl(ctx, this);
}

std::any Visitor::visitProg_Type_Name(plcst::PLCSTParser::Prog_Type_NameContext *ctx)
{
    return pou_decl::visitProg_Type_Name(ctx, this);
}

std::any Visitor::visitCommon_Var_Decl_Init(plcst::PLCSTParser::Common_Var_Decl_InitContext *ctx)
{
    return var_decl::visitCommon_Var_Decl_Init(ctx, this);
}

std::any Visitor::visitSimple_Spec(plcst::PLCSTParser::Simple_SpecContext *ctx)
{
    return var_decl::visitSimple_Spec(ctx, this);
}

std::any Visitor::visitElem_Type_Name(plcst::PLCSTParser::Elem_Type_NameContext *ctx)
{
    return var_decl::visitElem_Type_Name(ctx, this);
}

std::any Visitor::visitNumeric_Type_Name(plcst::PLCSTParser::Numeric_Type_NameContext *ctx)
{
    return var_decl::visitNumeric_Type_Name(ctx, this);
}

std::any Visitor::visitFunc_Var_Decls(plcst::PLCSTParser::Func_Var_DeclsContext *ctx)
{
    return var_decl::visitFunc_Var_Decls(ctx, this);
}